using DataAnalyticsTools.Models;
using System;
using System.Collections.Generic;
using System.Linq;

namespace DataAnalyticsTools.Core.ChangePointDetect
{
    /// <summary>
    /// CUSUM (Cumulative Sum) 变化点检测器
    /// 序贯分析技术，用于检测时间序列中的均值变化
    /// </summary>
    public static class CUSUMDetector
    {
        /// <summary>
        /// CUSUM算法参考值因子，通常设为标准差的一半
        /// 用于计算k参数：k = CUSUM_K_FACTOR * globalStd
        /// </summary>
        private const float CUSUM_K_FACTOR = 0.5f;

        /// <summary>
        /// 最大置信度上限
        /// </summary>
        private const float MAX_CONFIDENCE = 0.95f;

        /// <summary>
        /// 使用CUSUM方法检测变化点
        /// </summary>
        /// <param name="values">时间序列数据</param>
        /// <param name="windowSize">窗口大小</param>
        /// <param name="thresholdFactor">阈值调整因子（默认1.2，建议范围0.5-3.0）
        /// - 值越小: 更敏感，检测更多变化点
        /// - 值越大: 更保守，只检测显著变化</param>
        /// <returns>检测到的变化点列表</returns>
        public static List<ChangePoint> Detect(float[] values, int windowSize, float thresholdFactor = 1.2f)
        {
            var changePoints = new List<ChangePoint>();

            // 至少需要两个窗口的数据才能检测变化点
            if (values.Length < windowSize * 2)
                return changePoints;

            // 计算全局统计量
            float globalStd = MathHelper.CalculateStdDev(values);

            // 标准差太小，数据过于平稳，不检测变化点
            if (globalStd < MathHelper.MIN_STD_DEV)
                return changePoints;

            // CUSUM参数
            // k: 参考值，通常设为标准差的一半，用于过滤小的波动
            float k = CUSUM_K_FACTOR * globalStd;

            // h: 决策阈值，thresholdFactor 直接控制检测灵敏度
            float h = thresholdFactor * globalStd;

            float cusumPositive = 0f;
            float cusumNegative = 0f;

            // 使用初始窗口计算基准均值
            float referenceMean = values.Take(windowSize).Average();

            for (int i = windowSize; i < values.Length; i++)
            {
                // 使用滑动窗口更新参考均值（使用前windowSize个点）
                referenceMean = values.Skip(i - windowSize).Take(windowSize).Average();

                float deviation = values[i] - referenceMean;

                // 更新CUSUM统计量
                // 正向累积和：检测向上的变化
                cusumPositive = Math.Max(0, cusumPositive + deviation - k);
                // 负向累积和：检测向下的变化
                cusumNegative = Math.Max(0, cusumNegative - deviation - k);

                // 检测变化点：任一方向的累积和超过阈值
                if (cusumPositive > h || cusumNegative > h)
                {
                    float score = Math.Max(cusumPositive, cusumNegative);

                    // 计算变化幅度：使用向前和向后窗口的均值差
                    int forwardWindow = Math.Min(windowSize, values.Length - i);
                    float previousMean = referenceMean;
                    float currentMean = values.Skip(i).Take(forwardWindow).Average();
                    float magnitude = Math.Abs(currentMean - previousMean);

                    // 计算置信度：使用标准的统计方法
                    // 1. 统计显著性：score相对于阈值的比率
                    float statisticalSignificance = score / h;

                    // 2. 效应大小 (Cohen's d)：变化幅度相对于标准差
                    // Cohen's d = (mean1 - mean2) / pooled_std
                    // 对于CUSUM，我们使用全局标准差作为参考
                    float effectSize = magnitude / (globalStd + MathHelper.MIN_STD_DEV);

                    // 3. 综合置信度：结合统计显著性和效应大小
                    // 使用几何平均数，确保两者都需要达到一定水平
                    float rawConfidence = (float)Math.Sqrt(statisticalSignificance * effectSize);

                    // 4. 映射到[0, MAX_CONFIDENCE]区间
                    // 使用sigmoid-like函数：1 - exp(-x)
                    float confidence = Math.Min(MAX_CONFIDENCE, 1.0f - (float)Math.Exp(-rawConfidence));

                    // 根据实际均值变化判断方向
                    TrendDirection changeType = currentMean > previousMean
                        ? TrendDirection.Increasing
                        : TrendDirection.Decreasing;

                    changePoints.Add(new ChangePoint(
                        index: i,
                        score: score,
                        changeType: changeType,
                        magnitude: magnitude,
                        confidence: confidence
                    ));

                    // 重置CUSUM统计量，准备检测下一个变化点
                    cusumPositive = 0f;
                    cusumNegative = 0f;
                }
            }

            return changePoints;
        }
    }
}
